/**
 * Canvas 2D engine.
 * @param  {[type]} CANVAS [description]
 * @return {[type]}        [description]
 * @todo monitoring
 * @todo rendering of objects
 * @todo movement of objects of locations
 */
function GameEngine(CANVAS) {
    // define memory variables.
    var CONTEXT = CANVAS.getContext('2d'),
        EVENT_HANDLERS = {},
        OBJECT_BOUNDARY_MEMORY = {},
        OBJECT_MEMORY = [],
        OBJECT_MEMORY_DIRTY = true,
        PRESSED_KEYS = {},
        PRESSED_KEYS_CHECK_RATE = 5,
        STATE = 0;
        STATES = { RUNNING: 1, STOPPED: 0 };

    // define functional variables.
    var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
                window.webkitRequestAnimationFrame || window.msRequestAnimationframe,
        cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;

    this.addEvent = function addEvent(type, callback) {
        if (isArray(EVENT_HANDLERS[type])) {
            EVENT_HANDLERS[type].push(callback);
        } else if (isFunction(EVENT_HANDLERS[type])) {
            EVENT_HANDLERS[type] = [EVENT_HANDLERS[type], callback];
        } else {
            EVENT_HANDLERS[type] = callback;
        }
    };

    function checkForPressedKeys() {
        // loop through all pressed keys and fire their associated events.
        for (var key in PRESSED_KEYS) {
            fireEvent(key);
        }
        // continuously check for pressed keys.
        if (STATE == STATES.RUNNING) {
            setTimeout(checkForPressedKeys, PRESSED_KEYS_CHECK_RATE);
        }
    };

    function fireEvent(event, args) {
        if (isArray(EVENT_HANDLERS[event])) {
            for (var index = 0; index < EVENT_HANDLERS[event].length; index ++) {
                if (isFunction(EVENT_HANDLERS[event][index])) {
                    EVENT_HANDLERS[event][index].apply(EVENT_HANDLERS[event][index], args);
                }
            }
        } else if (isFunction(EVENT_HANDLERS[event])) {
            EVENT_HANDLERS[event].apply(event, args);
        }
    };

    function isArray(object) {
        return Object.prototype.toString.call(object) == '[object Array]';
    };

    function isDefined(object) {
        return typeof object != 'undefined';
    };

    function isFunction(object) {
        return typeof object == 'function';
    };

    function isObject(object) {
        return Object.prototype.toString.call(object) == '[object Object]';
    };

    function isString(object) {
        return typeof object == 'string';
    };

    function render() {
        // render screen only when necessary.
        if (OBJECT_MEMORY_DIRTY) {
            // erase the canvas.
            CONTEXT.clearRect(0, 0, CANVAS.width, CANVAS.height);

            // draw all objects within memory.
            for (var i = 0; i < OBJECT_MEMORY.length; i ++) {
                OBJECT_MEMORY[i].draw();
            }
            // reset the dirty variable.
            OBJECT_MEMORY_DIRTY = false;
        }

        // check to see if the game is currently running.
        if (STATE == STATES.RUNNING) {
            requestAnimationFrame(render);
        }
    };

    function start() {
        // set game state to 'running'.
        STATE = STATES.RUNNING;

        // begin rendering screen.
        render();
    };

    function stop() {
        // set game state to 'stopped'.
        STATE = STATES.STOPPED;
    };
};